package cn.org.atool.generator.javafile.template;

import cn.org.atool.generator.database.model.TableField;
import cn.org.atool.generator.database.model.TableSetter;
import com.squareup.javapoet.*;

import javax.lang.model.element.Modifier;
import java.util.Date;
import java.util.Objects;

import static cn.org.atool.generator.util.ClassNames.*;
import static cn.org.atool.generator.util.GeneratorHelper.isBlank;

public class DataMapFile extends AbstractTemplateFile {

    public DataMapFile(TableSetter table) {
        super(table);
        this.packageName = dmPackage(table);
        this.klassName = dmClassName(table);
    }

    public static ClassName dmName(TableSetter table) {
        return ClassName.get(dmPackage(table), dmClassName(table));
    }

    public static String dmPackage(TableSetter table) {
        return table.gc().getBasePackage() + ".dm";
    }

    public static String dmClassName(TableSetter table) {
        return table.getEntityPrefix() + "DataMap";
    }

    @Override
    protected void build(TypeSpec.Builder builder) {
        builder.addAnnotation(AnnotationSpec
                .builder(Test4J_ScriptTable)
                .addMember("value", "$S", table.getTableName())
                .build())
            .superclass(parameterizedType(Test4J_TableDataMap, dmName(table)));
        builder.addAnnotation(super.suppressWarnings("unused"));

        builder
            .addJavadoc("$T: 表(实体)数据对比(插入)构造器\n\n", super.className())
            .addJavadoc("@author Powered By Test4J");

        for (TableField field : table.getFields()) {
            builder.addField(this.buildField(field));
        }
        builder.addMethod(this.m_constructor1());
        builder.addMethod(this.m_constructor2());
        builder.addMethod(this.m_init());
        builder.addMethod(this.m_table_0());
        builder.addMethod(this.m_table_1());
        builder.addMethod(this.m_entity_0());
        builder.addMethod(this.m_entity_1());

        // Factory Class
        builder.addType(this.clazz_Factory());
    }

    private MethodSpec m_entity_1() {
        return this.initStaticMethod("entity")
            .addParameter(int.class, "size")
            .addStatement("return new $T(false, size)", dmName(table))
            .build();
    }

    private MethodSpec m_entity_0() {
        return this.initStaticMethod("entity")
            .addStatement("return new $T(false, 1)", dmName(table))
            .build();
    }

    private MethodSpec m_table_1() {
        return this.initStaticMethod("table")
            .addParameter(int.class, "size")
            .addStatement("return new $T(true, size)", dmName(table))
            .build();
    }

    private MethodSpec m_table_0() {
        return this.initStaticMethod("table")
            .addStatement("return new $T(true, 1)", dmName(table))
            .build();
    }

    private MethodSpec m_init() {
        MethodSpec.Builder builder = this.initPublicMethod().addAnnotation(Override.class);
        builder.addJavadoc("创建$L\n", this.klassName);
        builder.addJavadoc("初始化主键和gmtCreate, gmtModified, isDeleted等特殊值");
        for (TableField field : table.getFields()) {
            if (field.isPrimaryId()) {
                builder.addStatement("this.$L.autoIncrease()", field.getName());
            }
            if (field.isGmt()) {
                builder.addStatement("this.$L.values(new $T())", field.getName(), Date.class);
            }
            if (field.isDeleted() && Objects.equals(field.getJavaType(), CN_Boolean)) {
                builder.addStatement("this.$L.values(false)", field.getName());
            }
        }
        builder.addStatement("return this");
        return builder.build();
    }

    private FieldSpec buildField(TableField field) {
        FieldSpec.Builder builder = FieldSpec.builder(parameterizedType(Test4J_KeyValue, dmName(table)),
            field.getName(), Modifier.PUBLIC, Modifier.FINAL, Modifier.TRANSIENT);
        AnnotationSpec.Builder ab = AnnotationSpec.builder(Test4J_ColumnDef)
            .addMember("value", "$S", field.getColumnName())
            .addMember("type", "$S", field.getJdbcType());
        if (field.isPrimary()) {
            ab.addMember("primary", "true");
        }
        if (field.isPrimaryId()) {
            ab.addMember("autoIncrease", "true");
        }
        if (field.isNotNull()) {
            ab.addMember("notNull", "true");
        }
        if (!isBlank(field.getDefaults())) {
            ab.addMember("defaultValue", "$S", field.getDefaults());
        }

        builder.addAnnotation(ab.build());
        builder.initializer(CodeBlock.of("new KeyValue<>(this, $S, $S, supplier)", field.getColumnName(), field.getName()));
        return builder.build();
    }

    private MethodSpec m_constructor1() {
        return MethodSpec.constructorBuilder()
            .addParameter(boolean.class, "isTable")
            .addStatement("super($S, isTable)", table.getTableName())
            .build();
    }

    private MethodSpec m_constructor2() {
        return MethodSpec.constructorBuilder()
            .addParameter(boolean.class, "isTable")
            .addParameter(int.class, "size")
            .addStatement("super($S, isTable, size)", table.getTableName())
            .build();
    }

    private TypeSpec clazz_Factory() {
        return TypeSpec.classBuilder("Factory")
            .addModifiers(PUBLIC_STATIC)
            .superclass(ParameterizedTypeName.get(Test4J_TableBaseFactory, dmName(table)))
            .addMethod(this.m_factory_constructor())
            .build();
    }

    private MethodSpec m_factory_constructor() {
        return MethodSpec.constructorBuilder()
            .addModifiers(Modifier.PUBLIC)
            .addStatement("super($T.class)", dmName(table))
            .build();
    }

    @Override
    protected boolean isInterface() {
        return false;
    }

    private MethodSpec.Builder initStaticMethod(String method) {
        return MethodSpec.methodBuilder(method)
            .returns(dmName(table))
            .addModifiers(PUBLIC_STATIC);
    }

    private MethodSpec.Builder initPublicMethod() {
        return MethodSpec.methodBuilder("init")
            .returns(dmName(table))
            .addModifiers(Modifier.PUBLIC);
    }
}